from django.conf import settings
from django.http import HttpResponse

from django.contrib.auth.decorators import login_required

import simplejson as json
import time,math,base64,os

from django.contrib.auth.models import User
from torque.torrent.models import Torrent, Log, OrderedComment as Comment

import torque.api.comments as tac
import torque.api.files as taf
import torque.api.torrents as tat
import torque.api.xmlrpc as tax
import torque.api.logging as tal
import torque.api.errors as tae

sort_method = 'time_added'
sort_dir = 'DESC'

__default_sort_method = 'time_added'
__default_sort_dir = 'DESC'

def __set_sort_method(method):
    """ Set the global sort_method (the key on which to sort) """
    global sort_method
    sort_method = method
    
def __set_sort_dir(dir):
    """ Set the global sort_dir """
    global sort_dir
    sort_dir = dir
  
def __sort(a,b):
    """ Will sort two objects based on the global sort_method and sort_dir. Will 
    never return an error (even if invalid sort_method or sort_dir, will simply force
    defaults)."""
    global sort_method, sort_dir
    # We can make an assumption that a and b will have the same keys
    if sort_method not in a:
        sort_method = str(a.keys()[0])
    
    # Check to see if our key can be represented as an integer
    try:
        a_val = float(a[sort_method])
        b_val = float(b[sort_method])
    except ValueError:
        a_val = str(a[sort_method])
        b_val = str(b[sort_method])
        sort_type = "string"
    else:
        sort_type = "float"
    finally:
        if sort_dir == "ASC":
            if sort_type == "float":
                return  int(a_val - b_val);
            else: #string
                if b_val.lower() < a_val.lower():
                    return 1
                elif b_val.lower() > a_val.lower():
                    return -1
                else: #b_val == a_val
                    return 0
        else: #desc
            __set_sort_dir = ("DESC")
            if sort_type == "float":
                return int(b_val - a_val);
            else: # string
                if b_val.lower() > a_val.lower():
                    return 1
                elif b_val.lower() < a_val.lower():
                    return -1
                else: #b_val == a_val
                    return 0
                    
def __auth(request,admin=False):
    """ Returns a JSON HttpResponse object when a user is not authenticated, and None
    when a user is authenticated. Is used as a helper function in the various controller
    functions."""
    # Ensure user is authenticated, we dont want to redirect, just give error message.
    if not request.user.is_authenticated():
        return HttpResponse(tae.error['unauthenticated'])
	
	if admin:
		if not request.user.is_staff:
			return HttpResponse(tae.error['unauthorized'])
    
    return None
    
def _validOptions(optionsStr):
    try:
        optionsObj = json.loads(optionsStr)
    except:
        return None
    
    options = ['max_download_speed',
        'max_upload_speed',
        'max_connections',
        'max_upload_slots',
        'priorities_first_last_pieces',
        'auto_managed',
        'download_location'
    ]
    
    # Initialize options to specified value (or defaults if error or not provided)
    validOptionObj = {}
    for option in options:
        if option in optionsObj:
            try:
                type = settings.TORQUE['default_types']['options'][option]
                if type == 'float':
                    validOptionObj[option] = float(optionsObj[option])
                elif type == 'int':
                    vaidOptionObj[option] = int(optionsObj[option])
                elif type == 'str':
                    validOptionObj[option] = str(optionsObj[option])
                elif type == 'bool':
                    validOptionObj[option] = asbool(optionsObj[option])
                else:
                    validOptionObj[option] = optionsObj[option]
            except TypeError:
                validOptionObj[option] = settings.TORQUE['default']['options'][option]
        else:
            validOptionObj[option] = settings.TORQUE['default']['options'][option]

    if 'file_priorities' in options:
        validOptionObj['file_priorities'] = options['file_priorities']
    else:
        validOptionObj['file_priorities'] = []
        
    return validOptionObj
    
def localFiles(request):
    """ Retrieves a JSON object containing information on the trackers of the torrent 
    for the given hash."""
    
    folder = request.REQUEST.get('folder',None)
    if folder == None:
        folder = ''
    else:
        folder = base64.urlsafe_b64decode(folder.encode('utf-8'))
        

    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
    
    fileFetch = taf.getLocalFiles(folder)
    files = {'total': len(fileFetch),'files': fileFetch}
    return HttpResponse(json.dumps(files,encoding="latin-1"))
    
def client2database(request):
    client = tax.connect()
    
    torrents = client.get_torrents_status({},['name','save_path','hash'])
    
    for torrent in torrents.iteritems():
        hash = torrent[1]['hash']
        name = torrent[1]['name']
        simplename = tat.generateSimpleName(name)
        file =  torrent[1]['save_path']+ '.torrent'
        
        Torrent.objects.create(hash = hash, user_id = request.user, name = name, simplename = simplename, bandwidth = "0", status = 'A', file = file)
    
    return HttpResponse('DONE')
    
def getConfig(request):
    """ Gets the client configuration."""
    
    auth = __auth(request)
    if auth != None: return auth
    
    if not request.user.is_staff:
        return HttpResponse(tae.errors['unauthorized'])
    
    try: 
        client = tax.connect()
    except:
        return HttpResponse(tae.errors['client_connection'])
        
    try:
        config = client.get_config()
    except:
        return HttpRespone(tae.errors['client_command'] % 'filter_tree')
        
    return HttpResponse(json.dumps(config))
    
def status(request):
    """ Gets statistics information."""
    
    auth = __auth(request)
    if auth != None: return auth
    
    try: 
        client = tax.connect()
    except:
        return HttpResponse(tae.errors['client_connection'])
    
    info = {}
    info['users'] = User.objects.count()
    
  
    try:
        filter_tree = client.get_filter_tree()
    except:
        return HttpRespone(tae.errors['client_command'] % 'filter_tree')
    
    # It is output in a very odd way, but there it is.
    info['torrents'] = filter_tree['state'][0][1]
    
    try:
        stats = client.get_stats()
    except:
        return HttpRespone(tae.errors['client_command'] % 'get_stats')
        
    info['up_speed'] = stats['upload_rate']
    info['down_speed'] = stats['download_rate']
    
    if os.name == 'nt':
        hddout, hddin = os.popen4("fsutil volume diskfree "+settings.TORQUE['harddisk'])
        hdd_cmd = hddin.read()
        hdd_split = hdd_cmd.split("\n")
        info['hdd_available'] = int(hdd_split[0].split(': ')[1])
        info['hdd_total'] = int(hdd_split[1].split(': ')[1])
    else:
        hddout, hddin = os.popen4("df  "+settings.TORQUE['harddisk']+" | awk 'NR==2{print $2}'")
        info['hdd_total'] = int(hddin.readline()) * 1024
        
        hddout, hddin = os.popen4("df "+settings.TORQUE['harddisk']+" | awk 'NR==2{print $4}'")
        info['hdd_available'] = int(hddin.readline()) * 1024
    
    return HttpResponse(json.dumps(info))
    
def add(request):
    """ Given the torrents id (database id) add the torrent to the client. """
    import shutil
    file = request.REQUEST.get('file',None)
    options = request.REQUEST.get('options',None)
    if id == None:
        return HttpResponse(tae.error['missing_parameters'] % 'file')
        
    file = settings.TORQUE['torrent_path'] + file
    
    if options == None:
        optionObj = settings.TORQUE['defaults']['options']
    else:
        try:
            optionObj = base64.urlsafe_b64decode(options.encode("utf-8"))
        except:
            return HttpResponse(tae.error['invalid_parameters'] % 'base64:options')
        
        optionObj = _validOptions(optionObj)
        if optionObj == None:
            return HttpResponse(tae.error['invalid_parameters'] % 'options')

    auth = __auth(request)
    if auth != None: return auth
    
    details = tat.getDetails(file)
    if details == None:
        return HttpResponse(tae.error['invalid_parameters'] % 'file')
    
    name = details['name']
    hash = details['hash']
    simplename = tat.generateSimpleName(name)
    path = settings.TORQUE['torrent_path']+name+'.torrent'
    bandwidth = "0"
    
    try:
        shutil.move(file,path)
    except:
        return HttpResponse(tae.error['command_failed'] % 'move_torrent')
        
    try:
        torrent = Torrent.objects.create(hash = hash, user_id = request.user, name = name, simplename = name, file = path, status = 'A', bandwidth = bandwidth)
    except Torrent.DoesNotExist:
        return HttpResponse(tae.error['invalid_torrent'])
    
    try:
        client = tax.connect()
    except:
        torrent.delete()
        return HttpResponse(tae.error['client_connection'])
        
    try:
        client.add_torrent_file([path],[optionObj])
    except:
        torrent.delete()
        return HttpResponse(tae.error['client_command'] % 'add_torrent_file')
   
    return HttpResponse(tae.success['client_command'] % 'add_torrent_file')
        
  
def recheck(request,hash=None):
	""" Forces a recheck on the torrent's files """
	if hash == None:
		return HttpResponse(tae.error['missing_parameters'] % 'hash')

	auth = __auth(request)
	if auth != None: return auth

	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.error['client_connection'])

	try:
		client.force_recheck([hash])
	except:
		return HttpResponse(tae.error['client_command'] % 'recheck')
		
	return HttpResponse(tae.success['client_command'] % 'recheck')
	
def logs(request, hash=None):
	""" Retrieves a JSON object conainting the logs of the torrent for the given hash."""
	if hash == None:
		return HttpResponse(tae.error['missing_parameters'] % 'hash')
	
	# Ensure authroized user
	auth = __auth(request,admin=True)
	if auth != None: return auth
	
	logFetch = tal.getLogs(hash)
	logs = {'hash':hash,'logs':logFetch,'total':len(logFetch)}
	return HttpResponse(json.dumps(logs))
    
def trackers(request, hash=None):
    """ Retrieves a JSON object containing information on the trackers of the torrent 
    for the given hash."""
    if hash == None:
        return HttpResponse('{error:"Missing parameter (hash)!"}')
    
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
        
    trackerFetch = tat.getTrackers(hash)
    trackers = {'hash':hash,'trackers':trackerFetch,'total':len(trackerFetch)}
    return HttpResponse(json.dumps(trackers))
    
def peers(request, hash=None):
    """ Retrieves a JSON object containing information on the peers of the torrent 
    for the given hash."""
    if hash == None:
        return HttpResponse('{error:"Missing parameter (hash)!"}')
    
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
        
    peerFetch = tat.getPeers(hash)
    peers = {'hash':hash,'peers':peerFetch,'total':len(peerFetch)}
    return HttpResponse(json.dumps(peers))

def files(request, hash=None):
    """ Retrieves a JSON object containing information on the files of the torrent
    for the given hash."""
    if hash == None:
        return HttpResponse('{error:"Missing parameter (hash)!"}')
    
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
        
    fileFetch = tat.getFiles(hash)
    files = {'hash':hash,'files':fileFetch,'total':len(fileFetch)}
    return HttpResponse(json.dumps(files))


def comments(request, hash=None):
    """ Retrieves all comments for the given hash. Provides comments in the order of newest->oldest."""
    # Ensure valid hash was provided
    if hash == None:
        return HttpResponse('{error:"Missing parameter (hash)!"}')
    
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
    
    # Fetch the comments through tac
    commentFetch = tac.getComments(hash)
    
    # Format the comments for good json output
    comments = {'comments':commentFetch,'total':len(commentFetch)}
    return HttpResponse(json.dumps(comments))
    
def comments_add(request):
    """ Adds a comment to the database. Requires the hash of the torrent as comments are
    children of torrents. """
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth

    # Collect the user and input variables
    user = request.user
    hash = request.REQUEST.get('hash',None)
    user_comment = request.REQUEST.get('comment',None)
    
    if (hash == None) | (user_comment == None):
        return HttpResponse('{error:"Not enough arguments provided, need (hash,comment)."}')
    
    # Attempt to aquire the torrent object through provided hash.
    try:
        torrent = Torrent.objects.get(hash=hash, status='A')
    except Torrent.DoesNotExist:
        return HttpResponse('{error:"Invalid torrent hash."}')
    
    # Attempt to create the comment by adding it to the database.
    try:
        comment = Comment.objects.create(torrent_id = torrent, user_id = user, comment = user_comment)
    except:
        return HttpResponse('{error:"Failed adding comment."}')
    
    # Record event for this torrent hash.
    tal.event(request,hash,('User added comment: %s' % str(comment)))
    return HttpResponse('{success:"Added comment."}')
    
def comments_delete(request,id=None):
    """ Deletes a comment by provided id (gathered by comments). Will only allow staff,
    or the owner [writer] of the comment to complete this action. """
    if id == None:
        return HttpResponse('{error:"Missing parameter (id)!", errcodes:[1007,1005]}')
        
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
        
    try:
        comment = Comment.objects.get(id = id)
    except Comment.DoesNotExist:
        return HttpResponse('{error:"Comment does not exist.", errcodes:[1004,1005]}')
    else:
        hash = str(comment.torrent_id.hash)

    if (request.user == comment.user_id) | request.user.is_staff:
        try:
            str_comment = str(comment)
            comment.delete()
        except:
            return HttpResponse('{error:"Could not delete comment.",errcodes:[1005]}')
        
        tal.event(request,hash,('User deleted comment: %s' % str(str_comment)))		
        return HttpResponse('{success:"Comment deleted."}')
    else:
        return HttpResponse('{error:"Insufficient permissions to delete comment (not owner or staff).", errcodes:[1003,1005]}')

def start(request):
	""" Starts a torrent in the client """
	hash = request.REQUEST.get('hash',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')

	auth = __auth(request)
	if auth != None: return auth
	
	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.errors['client_connection'])
		
	try:
		client.resume_torrent([hash])
	except:
		return HttpResponse(tae.errors['client_command'] % 'resume_torrent')

	return HttpResponse(tae.success['client_command'] % 'resume_torrent')
	
def pause(request):
	""" Pauses a torrent in the client """
	hash = request.REQUEST.get('hash',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')

	auth = __auth(request)
	if auth != None: return auth
	
	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.errors['client_connection'])
		
	try:
		client.pause_torrent([hash])
	except:
		return HttpResponse(tae.errors['client_command'] % 'pause_torrent')

	return HttpResponse(tae.success['client_command'] % 'pause_torrent')

def getDetails(request):
	""" Retrieves detailed information on the torrent """
	hash = request.REQUEST.get('hash',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')
		
	auth = __auth(request)
	if auth != None: return auth
	
	keys = ['num_seeds','num_peers','hash','active_time','seeding_time',
		'time_added','next_announce','num_pieces','tracker_host','tracker_status']
		
	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.errors['client_connection'])
		
	try:
		details = client.get_torrent_status(hash,keys)
	except:
		return HttpResponse(tae.errors['client_command'] % 'get_torrent_status')
	
	detailsObj = {'details': [details]}
	
	return HttpResponse(json.dumps(detailsObj))
		
def getOptions(request):
	""" Retrieves the current options for the torrent """
	hash = request.REQUEST.get('hash',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')
		
	auth = __auth(request)
	if auth != None: return auth
	
	keys = ['max_upload_slots','max_upload_speed','max_download_speed',
		'max_connections','private','stop_at_ratio','stop_ratio','prioritize_first_last']
	
	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.errors['client_connection'])
		
	try:
		options = client.get_torrent_status(hash,keys)
	except:
		return HttpResponse(tae.errors['client_command'] % 'get_torrent_status')
		
	return HttpResponse(json.dumps(options))
	
def setOptions(request):
	""" Sets the options for the torrent """
	hash = request.REQUEST.get('hash',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')
		
	auth = __auth(request)
	if auth != None: return auth
	
	try:
		torrent = Torrent.objects.get(hash = hash)
	except Torrent.DoesNotExist:
		return HttpResponse(tae.errors['invalid_torrent'])
	
	
	
def remove(request):
	""" Removes a torrent in the client and possibly its data """
	hash = request.REQUEST.get('hash',None)
	data = request.REQUEST.get('data',None)
	
	if hash == None:
		return HttpResponse(tae.errors['missing_parameters'] % 'hash')
		
	if data == None:
		deleteData = False
	else:
		deleteData = True

	auth = __auth(request)
	if auth != None: return auth
	
	try:
		client = tax.connect()
	except:
		return HttpResponse(tae.errors['client_connection'])
		
	try:
		client.remove_torrent([hash],deleteData)
	except:
		return HttpResponse(tae.errors['client_command'] % 'remove_torrent')

	return HttpResponse(tae.success['client_command'] % 'remove_torrent')
	
	
def torrents(request):
    """ Retrieves detailed information about a torrent from both the client and the database. Takes
    many arguments that allow you to control pagination, sorting, columns, and filtering. 
    
    Valid parameter options and their purpose:
        -> keys: csv of the columns to show (see Documentation->Torrents->Keys)
        -> user: csv of all the users to show data from
        -> state: csv of all the states to show data from
        -> age: the number of hours to aged torrents from
        -> start: the item to start pagination at
        -> limit: how many items to return results from
    """
    global sort_dir, sort_method
    
    # Ensure authorized user
    auth = __auth(request)
    if auth != None: return auth
    
    try:
        client = tax.connect()
    except:
        return HttpResponse('{error:"Could not connect to client."}')
    
    key_string = request.REQUEST.get('keys',None)
    
    # Sorting Settings
    __set_sort_dir(request.REQUEST.get('dir','DESC'))
    __set_sort_method(request.REQUEST.get('sort','time_added'))
    
    # Filtering Settings
    search_string = request.REQUEST.get('search',None)
    user_string = request.REQUEST.get('user',None)
    state_string = request.REQUEST.get('state',None)
    age = request.REQUEST.get('age',None)
    
    # Pagination Settings
    start = request.REQUEST.get('start',None)
    limit = request.REQUEST.get('limit',None)

    
    if key_string == None:
        keys = ['hash']
    else:
        keys = key_string.split(',')
        if 'hash' not in keys:
            keys.append('hash')
    
    # These specify our filter rules
    hasFilter = False
    
    # Filtering by search
    if search_string == None:
        search = None
    else:
        hasFilter = True
        search_string = search_string.replace(' ','')
        search = tat.generateSimpleName(search_string)
    
    # Filtering by state
    if state_string == None:
        state = None
    else:
        hasFilter = True
        state = state_string.split(',')
        # Force the state key into query
        if 'state' not in keys:
            keys.append('state')
            
    if age != None:
        # Ensure our age is only a digit
        if age.isdigit():
            age = int(age)
            hasFilter = True
            if 'time_added' not in keys:
                keys.append('time_added')
        else:
            age = None
        
    if user_string == None:
        users = None
    else:
        hasFilter = True
        users = user_string.split(',')
        if 'user' not in keys:
            keys.append('user')
            
    if 'base64' in keys:
        if 'path' not in keys:
            keys.append('path')
    
    # XML-RPC Command
    try:
        torrents_raw = client.get_torrents_status({},keys)
    except:
        return HttpResponse('{errror:"Command failed."}')
    
    # make our list a more useable formats
    torrents = []
    for torrent in torrents_raw.iteritems():
        tempTorrent = torrent[1]
        if 'base64' in keys:
            tempTorrent['path'] = tempTorrent['path'].replace(settings.TORQUE['download_path'],'/')
            tempTorrent['base64'] = base64.urlsafe_b64encode(tempTorrent['path'])
        torrents.append(tempTorrent)
   
    # Only if we need to know the user do we need to query the DB
    if 'user' in keys:
        for torrent in torrents:
            torrent['user'] = tat.getTorrentOwner(torrent['hash'])
    
    # Only if we need to get the comments
    if 'comments' in keys:
        for torrent in torrents:
            torrent['comments'] = tac.getComments(torrent['hash'])
            
    if 'hasComments' in keys:
        for torrent in torrents:
            torrent['hasComments'] = tac.hasComments(torrent['hash'])
    
    # Only do filtering if we are actually filtering stuff
    if hasFilter:
        filteredTorrents = []
        for torrent in torrents:
            # Skip anything that does not match
            if state != None:
                if torrent['state'] not in state:
                    continue
            
            if age != None:
                # We calculate the age by the hours provided (turn it into seconds and
                # subtract from the current clock time [in seconds])
                if torrent['time_added'] < (time.time() - age * 3600):
                    continue
                
            if users != None:
                if torrent['user'] not in users:
                    continue
                    
            if search != None:
                if not tat.matchSimpleName(torrent['hash'],search):
                    continue
            
            # Torrent matches so store in new list.
            filteredTorrents.append(torrent)
    else:
        # We dont need to filter, aka everything matches.
        filteredTorrents = torrents
    
    # Generic info
    total = len(filteredTorrents)
    
    # Pagination Functionality
    paginate = False
    if (start != None) & (limit != None):
        if start.isdigit() & limit.isdigit():
            paginate = True
            start = int(start)
            limit = int(limit)
 
            stop = start + limit
           
            if stop >= (total+1):
                stop = total
                
            if start >= (total):
                count = 0
            else:
                count = stop-start
                
            pages = int(math.ceil(float(total) / float(limit)))
        else:
            paginate = False
            start = -1
            limit = -1
            pages = 1
            count = total
    else:
        paginate = False
        start = -1
        pages = 1
        limit = -1
        count = total
                
    # Sort the torrents using the sort function
    torrents = sorted(filteredTorrents,__sort)
    
    if paginate:
        torrents = torrents[start:stop]
    
    # Create a special dictionary for json output
    output = {'sort': sort_method,'pages':pages,'start':start,'limit':limit,'dir':sort_dir,'total':total,'count':count,'torrents':torrents}
    response = HttpResponse(json.dumps(output,indent=4))
    return response
